﻿using System;
using Simp.CodeModel;
using Simp.CodeModel.CoreFunctions;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Simp.GraphModel
{
	/// <summary>Perform a rotation on a graph element.</summary>
	/// <remarks>
	/// A composition of rotations sums the angle of rotation.
	/// A rotation on a polar coordinate adds the angle of rotation on the polar coordinate angle.
	/// </remarks>
	public class RotateFunction : IElementProducerFunction
	{
		private static readonly string[] PARAMETER_NAMES = new[] { "element", "angle" };

		#region IFunction Members
		string IFunction.FunctionName
		{
			get { return "Rotate"; }
		}

		string[] IFunction.ParameterNames
		{
			get { return PARAMETER_NAMES; }
		}

		IFunctionParameterBinding[] IFunction.DefaultParameterValues
		{
			get { return FunctionHelper.EmptyBindings; }
		}

		IFunction IFunction.Simplify(SimplificationMode simplificationMode, params IFunction[] parameters)
		{
			FunctionHelper.ValidateParameters(this, parameters);

			IFunction element = parameters[0];
			IFunction angle = parameters[1];

			BoundFunctionCalling subRotation = FunctionHelper.TryGetBoundFunction<RotateFunction>(element);

			if (subRotation == null)
			{
				BoundFunctionCalling polar = FunctionHelper.TryGetBoundFunction<PolarFunction>(element);

				if (polar == null)
				{
					if (simplificationMode != SimplificationMode.AllowApproximation)
					{
						return null;
					}
					else
					{
						BoundFunctionCalling point = FunctionHelper.TryGetBoundFunction<PointFunction>(element);

						if (point == null)
						{
							return null;
						}
						else
						{
							return SimplifyCartesian(angle, point);
						}
					}
				}
				else
				{
					return SimplifyPolar(simplificationMode, angle, polar);
				}
			}
			else
			{
				return SimplifyRotationComposition(simplificationMode, angle, subRotation);
			}
		}

		IFunction IFunction.AcceptVisitor(IFunctionVisitor visitor)
		{
			return null;
		}
		#endregion

		#region IElementProducerFunction Members
		UIElement IElementProducerFunction.Produce(params IFunction[] parameters)
		{
			FunctionHelper.ValidateParameters(this, parameters);

			IFunction element = parameters[0];
			IFunction angle = parameters[1];
			INumericFunction numericRotationAngle = angle as INumericFunction;

			if (angle != null)
			{
				UIElement uiElement = GraphicHelper.Produce(element);

				if (uiElement != null)
				{
					GraphicHelper.AddTransform(
						uiElement,
						new RotateTransform { Angle = numericRotationAngle.GetReal(true).Value });

					return uiElement;
				}
			}

			return null;
		}
		#endregion

		private static IFunction SimplifyRotationComposition(
			SimplificationMode simplificationMode, IFunction angle, BoundFunctionCalling subRotation)
		{
			IFunction subElement = subRotation.FunctionParameters[0];
			IFunction subAngle = subRotation.FunctionParameters[1];
			IFunction angleAddition = new BoundFunctionCalling(new AdditionFunction(), angle, subAngle);
			IFunction newRotation = new BoundFunctionCalling(new RotateFunction(), subElement, angleAddition);
			IFunction simplifiedRotation = FunctionHelper.ApplySimplification(simplificationMode, newRotation);

			return simplifiedRotation;
		}

		private static IFunction SimplifyPolar(
			SimplificationMode simplificationMode, IFunction angle, BoundFunctionCalling polar)
		{
			IFunction r = polar.FunctionParameters[0];
			IFunction theta = polar.FunctionParameters[1];
			IFunction angleAddition = new BoundFunctionCalling(new AdditionFunction(), angle, theta);
			IFunction newPolar = new BoundFunctionCalling(new PolarFunction(), r, angleAddition);
			IFunction simplifiedPolar = FunctionHelper.ApplySimplification(simplificationMode, newPolar);

			return simplifiedPolar;
		}

		private IFunction SimplifyCartesian(IFunction rotationAngle, BoundFunctionCalling point)
		{
			INumericFunction numericRotationAngle = rotationAngle as INumericFunction;

			if (numericRotationAngle == null)
			{
				return null;
			}
			else
			{
				double r;
				double theta;

				if (PointFunction.TryGetPolar(point.FunctionParameters[0], point.FunctionParameters[1], out r, out theta))
				{
					double newTheta = theta + numericRotationAngle.GetReal(true).Value;
					IFunction newPolar =
						new BoundFunctionCalling(new PolarFunction(), new RealFunction(r), new RealFunction(newTheta));

					return newPolar;
				}
				else
				{
					return null;
				}
			}
		}
	}
}